package com.espirit.eap.relationship;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Transient;

import org.apache.commons.lang.StringUtils;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Index;
import org.hibernate.annotations.Table;

import com.espirit.eap.PinyinAware;
import com.espirit.eap.annotation.ModelDisplayProperty;
import com.espirit.eap.sa.User;
import com.googlecode.cswish.annotation.Protected;

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
//@DiscriminatorColumn(name = "partyType", discriminatorType = DiscriminatorType.STRING)
@ModelDisplayProperty("name")
@Table(indexes={
		@Index(name="idx_code", columnNames={"code"} ),
		@Index(name="idx_pinyin", columnNames={"pinyin"} ),
		@Index(name="idx_name", columnNames={"name"} )},appliesTo = "espirit_party")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@BatchSize(size=10)
public class Party implements Serializable, PinyinAware{
	private static final long serialVersionUID = -5926159869445618212L;
	private Integer id;
	private String code;
	private String name;
	private String description;
	private PartyType partyType;
	private List<PartyRelationship> children;
	private List<PartyRelationship> parents;
	
	private String pinyin;
	
	@Id
	@GeneratedValue(strategy=GenerationType.TABLE)
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getCode() {
		return code;
	}
	public void setCode(String code) {
		this.code = code;
	}
	@Column(nullable=false)
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Column(length=2000)
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	
	public void setChildren(List<PartyRelationship> children) {
		this.children = children;
	}
	
	@OneToMany(targetEntity=PartyRelationship.class,orphanRemoval = true,fetch=FetchType.LAZY,cascade=CascadeType.ALL, mappedBy="partyParent")
	public List<PartyRelationship> getChildren() {
		return children;
	}
	
	public void setParents(List<PartyRelationship> parents) {
		this.parents = parents;
	}

	@OneToMany(targetEntity=PartyRelationship.class, orphanRemoval = true, cascade=CascadeType.MERGE,mappedBy="partyChild")
	public List<PartyRelationship> getParents() {
		return parents;
	}	
	
	public void setPartyType(PartyType partyType) {
		this.partyType = partyType;
	}
	@ManyToOne(targetEntity=PartyType.class,fetch=FetchType.LAZY)
	@Protected
	public PartyType getPartyType() {
		return partyType;
	}
	
	
	@Transient
	public Integer getParentId() {		// parents[0].partyParent.id
		if (getParents().size() > 0) {
			Party parent = getParents().get(0).getPartyParent();
			if (parent != null) {
				return parent.getId();
			}
		}
		return null;
	}
	
	@Transient
	@Protected
	public List<PartyRelationship> safeGetParents() {
		if (parents == null) {
			parents = new ArrayList<PartyRelationship>();
		}
		return parents;
	}
	@Transient
	@Protected
	public List<PartyRelationship> safeGetChildren() {
		if (children == null) {
			children = new ArrayList<PartyRelationship>();
		}
		return children;
	}
	
	
	@SuppressWarnings("unchecked")
	@Transient
	@Protected
	public <C extends Party> List<C> getChildren(Class<C> childClazz){
		List<C> results = null;
		if (getChildren() == null) return null;
		for(PartyRelationship prs : getChildren()){
			if (prs.valid()){
				Party partyChild = prs.getPartyChild();
				if (partyChild != null && childClazz.isAssignableFrom(partyChild.getClass())){
					if (results == null){
						results = new ArrayList<C>();
					}
					if (!results.contains(partyChild)){
						results.add((C)partyChild);
					}
				}
			}
		}
		return results;
	}
	
	@Transient
	@Protected
	public <C extends Party> PartyRelationship contains(C partyChild,PartyRelationshipType prst){
		if (getChildren() == null) return null;
		for(PartyRelationship prs : getChildren()){
			if (prs.valid()){
				Party child = prs.getPartyChild();
				if (partyChild == child && prst == prs.getPartyRelationshipType()) {
					return prs;
				}
			}
		}
		return null;
	}
		
	@SuppressWarnings("unchecked")
	@Transient
	@Protected
	public <C extends Party> Set<C> getDescendentChildren(Class<C> childClazz){
		Set<C> results = null;
		if (getChildren() == null) return Collections.EMPTY_SET;
		for(PartyRelationship prs : getChildren()){
			if (prs.valid()){
				if (results == null){
					results = new HashSet<C>();
				}
				Party partyChild = prs.getPartyChild();
				if (partyChild != null && childClazz.isAssignableFrom(partyChild.getClass())){
					results.add((C)partyChild);
					Set<C> descentdentchildren = partyChild.getDescendentChildren(childClazz);
					if (descentdentchildren != null){
						descentdentchildren.removeAll(results);
						results.addAll(descentdentchildren);
					}
				}
			}
		}
		return results;
	}

	
	@SuppressWarnings("unchecked")
	@Transient
	@Protected
	public <C extends Party> List<C> getParents(Class<C> parentClazz){
		List<C> results = null;
		if (getParents() == null) return null;
		for(PartyRelationship prs : getParents()){
			if (prs.valid()){
				if (results == null){
					results = new ArrayList<C>();
				}
				Party partyParent = prs.getPartyParent();
				if (partyParent == null) continue;
				
				if (parentClazz.isAssignableFrom(partyParent.getClass())){
					results.add((C)partyParent);
				}
			}
		}
		return results;
	}
	
	@Transient
	@Protected
	public <C extends Party> List<PartyRelationship> getChildrenRelationship(Class<C> childClazz){
		List<PartyRelationship> results = null;
		if (getChildren() == null) return null;
		for(PartyRelationship prs : getChildren()){
			if (prs.valid()){
				Party partyChild = prs.getPartyChild();
				if (childClazz.isAssignableFrom(partyChild.getClass())){
					if (results == null){
						results = new ArrayList<PartyRelationship>();
					}
					results.add(prs);
				}
			}
		}
		return results;
	}
	
	@Transient
	@Protected
	public <C extends Party> List<PartyRelationship> getDescendentChildrenRelationship(Class<C> childClazz){
		List<PartyRelationship> results = null;
		if (getChildren() == null) return null;
		for(PartyRelationship prs : getChildren()){
			if (prs.valid()){
				if (results == null){
					results = new ArrayList<PartyRelationship>();
				}
				Party partyChild = prs.getPartyChild();
				if (this.getClass().isAssignableFrom(partyChild.getClass())){
					results.add(prs);
					List<PartyRelationship> descentdentchildren = partyChild.getDescendentChildrenRelationship(childClazz);
					results.addAll(descentdentchildren);
				}
			}
		}
		return results;
	}
	
	public PartyRelationship addChildParty(Party partyChild) {
		PartyRelationship prs = new PartyRelationship();
		prs.setPartyChild(partyChild);
		prs.setPartyParent(this);
		return prs;
	}
	public <T extends Party> void addChildParties(List<T> children){
		if (children == null) return ;
		for(Party child : children){
			addChildParty(child);
		}
	}

	public <T extends Party> void removeChildren(Class<T> clazz) {
		if (children == null) return ;
		for (Iterator<PartyRelationship> it = children.iterator();it.hasNext();){
			PartyRelationship prs = it.next();
			if (prs.valid() && clazz.isAssignableFrom(prs.getPartyChild().getClass())){
				it.remove();
			}
		}
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Party other = (Party) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}
	
	public String getPinyin() {
		return pinyin;
	}
	public void setPinyin(String pinyin) {
		this.pinyin = pinyin;
	}
	
	@Transient
	@Override
	public String source() {
		return name;
	}
	
	
	//shared bu user ,party
	
	private String userIds;
	private List<Integer> userIdList;
	@Transient
	public String getUserIds() {
		if (userIds == null) {
			List<Integer> userIdList = getUserIdList();
			userIds = StringUtils.join(userIdList, ',');
		}
		return userIds;
	}
	public void setUserIds(String userIds) {
		this.userIds = userIds;
		String[] ids = StringUtils.split(userIds, ',');
		userIdList = new ArrayList<Integer>(ids.length);
		for (String id : ids) {
			userIdList.add(new Integer(id));
		}
	}

	@Transient
	private List<Integer> getUserIdList() {
		if (userIdList == null) {
			userIdList = new ArrayList<Integer>();
			List<User> users = getParents(User.class);
			if (users != null){
				for(User user : users){
					userIdList.add(user.getId());
				}
			}
		}
		return userIdList;
	}
}
